import { _decorator, sys } from 'cc';
import { BCGameLog } from '../../utils/BCGameLog';
import { BCProjectDefines } from '../../utils/BCProjectDefines';

/**
 * Predefined variables
 * Name = WSInterface
 * DateTime = Mon Jan 24 2022 15:27:18 GMT+0800 (中国标准时间)
 * Author = 风流鼻涕淌
 * FileBasename = WSInterface.ts
 * FileBasenameNoExtension = WSInterface
 * URL = db://assets/Main/script/Controller/network/WSInterface.ts
 * ManualUrl = https://docs.cocos.com/creator/3.4/manual/zh/
 *
 */

/**
 * websocket错误码
 * 1000 -连接正常关闭
 * 1001 -终端离开
 * 1002 -协议错误
 * 1003 -不支持的数据类型
 * 1005 -关闭时没有提供状态码。
 * 1006 -连接意外关闭
 * 1007 -数据格式不正确
 * 1008 -消息太大
 * 1009 -消息内容不合法
 * 1010 -连接超时
 * 1011 -服务器内部错误
 * 1012 -服务器重启
 * 1013 -临时性服务器过载，稍后再试
 * 1014 -错误网关，网关服务器收到上游服务器的无效响应，类似于Http的502
 * 1015 -TLS握手失败
 */

const blobToArrayBuffer = (blob, callback) => {
	let reader = new FileReader();
	reader.onload = function () {
		return callback(this.result);
	}
	reader.readAsArrayBuffer(blob);
}


export abstract class BCWSInterface extends WebSocket{
	private readonly m_heartbeatTime: number = 3000; 	//心跳时间间隔，单位毫秒
	private m_heartbeatTimer: number = null;					//心跳丢失计时器
	private m_closeTimer = null;

	protected abstract processRecvData(data: Uint8Array);
	protected abstract onOpen();								//ws 连接成功
	private m_closeCb: (isExit: boolean, reason?: string)=>void = null;
	private m_isExit: boolean = false;

	constructor(url: string, protocols: string[], openCb?: () => void, closeCb?: (isExit: boolean, reason?: string)=>void){
		super(url+"?token=" + protocols[0] + "&" + "version=" + protocols[1]);
		BCGameLog.warn("[WSInterface]", "start connect:"+url);

		this.m_closeCb = closeCb;

		this.onopen = () => {
			BCGameLog.warn("[WSInterface]", "WebSocket onopen:" + url);
			this.m_isExit = false;
			this.startHeartbeat();
			this.onOpen();
			openCb && openCb();
		}

		this.onclose = (event: CloseEvent) => {
			BCGameLog.warn("[WSInterface]", `WebSocket closed:${url}, code:${event.code}, msg:${event.reason}`);
			this.preCloseConnect(this.m_isExit, event.reason);
		}

		this.onerror = (event: ErrorEvent)=>{
			BCGameLog.warn("[WSInterface]", `WebSocket error:${url}, msg:${event.message}`);
			this.preCloseConnect(this.m_isExit);
		}

		this.onmessage = (event: MessageEvent) => {
			this.onRecvData(event.data);
		}
	}

	protected onRecvData(data: any) {
		if (sys.isBrowser){
			blobToArrayBuffer(data, (arrayBuffer) => {
				var u8a = new Uint8Array(arrayBuffer);
				this.processRecvData(u8a);
			});
		} else {
			this.processRecvData(new Uint8Array(data));
		}
	}

	protected startHeartbeat() {
		this.m_heartbeatTimer = setInterval(() => {
			this.sendHeartbeat();
			this.m_closeTimer = setTimeout(() => {
				this.closeConnect(BCProjectDefines.WsCloseType.Error);
			}, this.m_heartbeatTime * 2);
		}, this.m_heartbeatTime);
	}

	private stopHeartbeat(){
		if(this.m_heartbeatTimer){
			clearInterval(this.m_heartbeatTimer);
			this.m_heartbeatTimer = null;
		}
		if (this.m_closeTimer) {
			clearTimeout(this.m_closeTimer);
			this.m_closeTimer = null;
		}
	}

	protected abstract sendHeartbeat();			//发送心跳消息
	protected onRecvHeartbeat(data?: any) {
		if (this.m_closeTimer) {
			clearTimeout(this.m_closeTimer);
			this.m_closeTimer = null;
		}
	}


	protected abstract processSendMsg(cmd: any, data?: any);


	sendMsg(msg: Uint8Array) {
		if (this.readyState == WebSocket.CLOSING){
			BCGameLog.warn("[WSInterface]", "消息发送失败，websocket连接已关闭！");
			return;
		}
		if (this.readyState != WebSocket.OPEN) {
			BCGameLog.warn("[WSInterface]", "消息发送失败，websocket连接丢失！");
			return;
		}
		this.send(msg);
	}

	protected preCloseConnect(isExit: boolean, reason?: string){
		this.stopHeartbeat();
		this.onclose = ()=>{};
		this.onmessage = ()=>{};
		this.onerror = ()=>{};
		this.m_closeCb && this.m_closeCb(isExit, reason);
		this.m_closeCb = null;
	}

	/**
	 * 关闭
	 */
	closeConnect(code: number){
		this.m_isExit = code == BCProjectDefines.WsCloseType.Exit;
		this.preCloseConnect(this.m_isExit);
		if(this.readyState==WebSocket.OPEN){
			this.close();
		}
	}
}
